home *** CD-ROM | disk | FTP | other *** search
/ The Fatted Calf / The Fatted Calf.iso / Unix / CNews / Source / expire / expire.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-04-18  |  28.9 KB  |  1,336 lines

  1. /*
  2.  * expire - expire old news
  3.  *
  4.  * One modest flaw:  links are not preserved in archived copies, i.e. you
  5.  * get multiple copies of multiply-posted articles.  Since link preservation
  6.  * is arbitrarily hard when control files get complex, to hell with it.
  7.  */
  8.  
  9. #include <stdlib.h>
  10. #include <stdio.h>
  11. #include <ctype.h>
  12. #include <string.h>
  13. #include <errno.h>
  14. #include "fixerrno.h"
  15. #include <time.h>
  16. #include <signal.h>
  17. #include <sys/types.h>
  18. #include <sys/timeb.h>
  19. #include <sys/stat.h>
  20. #include "libc.h"
  21. #include "news.h"
  22. #include "config.h"
  23. #include "fgetmfs.h"
  24. #include "case.h"
  25. #include "dbz.h"
  26. #include "ngmatch.h"
  27.  
  28. /* basic parameters of time, used in back() (and debugging code) */
  29. #ifndef EPOCH
  30. #define    EPOCH    ((time_t)0)    /* the origin of time_t */
  31. #endif
  32. #ifndef FOREVER
  33. #define    FOREVER    1e37        /* FOREVER > max value of time_t */
  34. #endif
  35. #define    DAY    ((double)24*60*60)
  36.  
  37. /* structure for expiry-control records */
  38. struct ctl {
  39.     struct ctl *next;
  40.     char *groups;        /* newsgroups */
  41.     NGPAT *pat;        /* parsed "groups", if actually a pattern */
  42.     int ismod;        /* moderated? */
  43. #        define    UNMOD    'u'
  44. #        define    MOD    'm'
  45. #        define    EITHER    'x'
  46.     time_t retain;        /* earliest arrival date not expired */
  47.     time_t normal;        /* earliest not expired in default case */
  48.     time_t purge;        /* latest arrival date always expired */
  49.     char *dir;        /* Archive dir or NULL. */
  50. };
  51.  
  52. /* header for internal form of control file */
  53. struct ctl *ctls = NULL;
  54. struct ctl *lastctl = NULL;
  55.  
  56. /*
  57.  * Headers for lists by newsgroup, derived (mostly) from active file.
  58.  * Hashing is by length of newsgroup name; this is quick and works well,
  59.  * and there is no simple variation that does much better.
  60.  */
  61. #define    NHASH    80
  62. struct ctl *ngs[NHASH] = { NULL };
  63.  
  64. struct ctl *holdover = NULL;    /* "/expired/" control record */
  65. struct ctl *bounds = NULL;    /* "/bounds/" control record */
  66.  
  67. int debug = 0;            /* for inews routines */
  68. int expdebug = 0;        /* expire debugging */
  69.  
  70. int printexpiring = 0;        /* print info line for expiring articles? */
  71. char *defarch = NULL;        /* default archive dir */
  72. int spacetight = 0;        /* error-recovery actions remove evidence? */
  73.  
  74. char *subsep = "~";        /* subfield separator in middle field */
  75. int checkonly = 0;        /* check control information only */
  76. int testing = 0;        /* testing only, leave articles alone */
  77. int leaders = 0;        /* only first link ("leader") is hard link */
  78. int verbose = 0;        /* report statistics */
  79. int rebuild = 1;        /* rebuild history files */
  80. int holdarch = 0;        /* hold rather than archiving */
  81. int getdgrump = 0;        /* report un-getdate()able expiry dates */
  82. char *histdir = NULL;        /* where to find history files */
  83.  
  84. long nkept = 0;            /* count of articles not expired */
  85. long ngone = 0;            /* count of articles removed (no links left) */
  86. long nresid = 0;        /* count of residual entries kept */
  87. long narched = 0;        /* count of links archived */
  88. long njunked = 0;        /* count of links just removed */
  89. long nmissing = 0;        /* count of links missing at cp/rm time */
  90.  
  91. char dont[] = "don't";        /* magic cookie for whereexpire() return */
  92.  
  93. time_t now;            /* set once in startup, as reference */
  94. struct timeb ftnow;        /* ftime() result for getdate() */
  95. #define    NODATE    ((time_t)(-1))    /* time_t value indicating date not given */
  96. time_t latest =    0;        /* most recent arrival date */
  97.  
  98. char subject[200] = "";        /* Subject line for -p, minus header */
  99.  
  100. /* Buffer etc. for readline and friends. */
  101. char rlbuf[BUFSIZ];
  102. int rlnleft = 0;
  103. char *rest;
  104. int nlocked = 0;        /* has readline() locked the news system? */
  105.  
  106. /*
  107.  * Archive-copying buffer.
  108.  * 8KB buffer is large enough to take most articles at one gulp,
  109.  * and also large enough for virtual certainty of getting the
  110.  * Subject: line in the first bufferload.
  111.  */
  112. #ifdef SMALLMEM
  113. char abuf[2*1024];        /* expire reported to be tight on 11 */
  114. #else
  115. char abuf[8*1024];
  116. #endif
  117.  
  118. char *progname;
  119.  
  120. extern long atol();
  121. extern double atof();
  122. extern struct tm *gmtime();
  123. extern time_t time();
  124.  
  125. extern time_t getdate();
  126.  
  127. /* forwards */
  128. FILE *eufopen();
  129. void eufclose();
  130. void euclose();
  131. char *whereexpire();
  132. time_t back();
  133. void checkadir();
  134. void fail();
  135. void die();
  136. void control();
  137. void prime();
  138. void doit();
  139. void cd();
  140. char *doline();
  141. time_t readdate();
  142. char *doarticle();
  143. void warning();
  144. void complain();
  145. void printstuff();
  146. void expire();
  147. char *readline();
  148. void mkparents();
  149. void getsubj();
  150. void refill();
  151. void printlists();
  152. void pctl();
  153. void fillin();
  154. void ctlline();
  155. char *strvsave();
  156.  
  157. /*
  158.  - main - parse arguments and handle options
  159.  */
  160. main(argc, argv)
  161. int argc;
  162. char *argv[];
  163. {
  164.     register int c;
  165.     register int errflg = 0;
  166.     register FILE *cf;
  167.     extern int optind;
  168.     extern char *optarg;
  169.  
  170.     progname = argv[0];
  171.     now = time((time_t *)NULL);
  172.     ftime(&ftnow);
  173.  
  174.     while ((c = getopt(argc, argv, "pa:sF:cn:tlvrhgH:d")) != EOF)
  175.         switch (c) {
  176.         case 'p':    /* print info line for archived articles */
  177.             printexpiring = 1;
  178.             break;
  179.         case 'a':    /* archive in this directory */
  180.             defarch = optarg;
  181.             break;
  182.         case 's':    /* maximize space during error recovery */
  183.             spacetight = 1;
  184.             break;
  185.         case 'F':    /* subfield separator in middle field */
  186.             subsep = optarg;
  187.             break;
  188.         case 'c':    /* check control-file format only */
  189.             checkonly = 1;
  190.             break;
  191.         case 'n':    /* set value of "now" for testing */
  192.             now = atol(optarg);
  193.             break;
  194.         case 't':    /* testing, do not mess with articles */
  195.             testing = 1;
  196.             break;
  197.         case 'l':    /* leaders */
  198.             leaders = 1;
  199.             break;
  200.         case 'v':    /* verbose -- report some statistics */
  201.             verbose = 1;
  202.             break;
  203.         case 'r':    /* suppress history-file rebuild */
  204.             rebuild = 0;
  205.             break;
  206.         case 'h':    /* hold all files meant to be archived */
  207.             holdarch = 1;
  208.             break;
  209.         case 'g':    /* report getdate() failures on expiry dates */
  210.             getdgrump = 1;
  211.             break;
  212.         case 'H':    /* where to find history files */
  213.             histdir = optarg;
  214.             break;
  215.         case 'd':    /* debug */
  216.             expdebug = 1;
  217.             break;
  218.         case '?':
  219.         default:
  220.             errflg++;
  221.             break;
  222.         }
  223.     if (errflg || optind < argc-1) {
  224.         fprintf(stderr, "Usage: %s [-p] [-s] [-c] [-a archdir] [ctlfile]\n",
  225.                                 progname);
  226.         exit(2);
  227.     }
  228.     if (expdebug)
  229.         setbuf(stderr, (char *)NULL);
  230.  
  231.     if (optind < argc) {
  232.         cf = eufopen(argv[optind], "r");
  233.         control(cf);
  234.         (void) fclose(cf);
  235.     } else
  236.         control(stdin);
  237.     prime(ctlfile("active"));
  238.  
  239.     if (expdebug)
  240.         printlists();
  241.     if (histdir == NULL)
  242.         histdir = strsave(ctlfile((char *)NULL));
  243.     if (defarch != NULL)
  244.         checkadir(defarch);
  245.     if (checkonly)
  246.         exit(0);
  247.  
  248.  
  249.     (void) umask(newsumask());
  250.     doit();            /* side effect: newslock() */
  251.     newsunlock();
  252.  
  253.     if (latest > time((time_t *)NULL)) {
  254.         complain("some article arrival dates are in the future!", "");
  255.         complain("\tis your system clock set wrong?", "");
  256.     }
  257.  
  258.     if (verbose) {
  259.         fprintf(stderr, "%ld kept, %ld expired\n", nkept, ngone);
  260.         fprintf(stderr, "%ld residual lines\n", nresid);
  261.         fprintf(stderr, "%ld links archived, %ld junked, %ld missing\n",
  262.                         narched, njunked, nmissing);
  263.     }
  264.     exit(0);
  265. }
  266.  
  267. /*
  268.  - control - pick up a control file
  269.  */
  270. void
  271. control(f)
  272. register FILE *f;
  273. {
  274.     char line[200];        /* long enough for any sane line */
  275.     register char *p;
  276.     register int gotone = 0;
  277.  
  278.     while (fgets(line, sizeof(line), f) != NULL) {
  279.         p = &line[strlen(line) - 1];
  280.         if (*p != '\n')
  281.             die("control line `%.30s...' too long", line);
  282.         *p = '\0';
  283.         if (line[0] != '#')
  284.             ctlline(line);
  285.         gotone = 1;
  286.     }
  287.  
  288.     if (!gotone)
  289.         die("control file empty!", "");
  290. }
  291.  
  292. /*
  293.  - ctlline - process one control-file line
  294.  */
  295. void
  296. ctlline(ctl)
  297. char *ctl;
  298. {
  299.     register struct ctl *ct;
  300.     char *field[4];
  301.     char datebuf[50];
  302.     char *dates[3];
  303.     register int nf;
  304.     int ndates;
  305.  
  306.     nf = split(ctl, field, 4, "");
  307.     if (nf == 0)
  308.         return;        /* blank line */
  309.     if (nf != 4)
  310.         die("control line `%.20s...' hasn't got 4 fields", ctl);
  311.  
  312.     ct = (struct ctl *)malloc(sizeof(struct ctl));
  313.     if (ct == NULL)
  314.         fail("out of memory for control list", "");
  315.  
  316.     ct->groups = strsave(field[0]);
  317.     ct->pat = ngparse(strsave(ct->groups));
  318.     if (ct->pat == NULL)
  319.         die("can't parse control file pattern `%s'", ct->groups);
  320.     if (STREQ(field[1], "m"))
  321.         ct->ismod = MOD;
  322.     else if (STREQ(field[1], "u"))
  323.         ct->ismod = UNMOD;
  324.     else if (STREQ(field[1], "x"))
  325.         ct->ismod = EITHER;
  326.     else
  327.         die("strange mod field `%s' in control file", field[1]);
  328.  
  329.     if (strlen(field[2]) > sizeof(datebuf)-1)
  330.         die("date specification `%s' too long", field[2]);
  331.     (void) strcpy(datebuf, field[2]);
  332.     ndates = split(datebuf, dates, 3, "-");
  333.     switch (ndates) {
  334.     case 3:
  335.         ct->retain = back(dates[0]);
  336.         ct->normal = back(dates[1]);
  337.         ct->purge = back(dates[2]);
  338.         break;
  339.     case 2:
  340.         ct->retain = (bounds != NULL) ? bounds->retain : back("0");
  341.         ct->normal = back(dates[0]);
  342.         ct->purge = back(dates[1]);
  343.         break;
  344.     case 1:
  345.         ct->retain = (bounds != NULL) ? bounds->retain : back("0");
  346.         ct->normal = back(dates[0]);
  347.         ct->purge = (bounds != NULL) ? bounds->purge : back("never");
  348.         break;
  349.     default:
  350.         die("invalid date specification `%s'", field[2]);
  351.         /* NOTREACHED */
  352.         break;
  353.     }
  354.     if (ct->retain < ct->normal && ndates <= 2)    /* stretch defaults */
  355.         ct->retain = ct->normal;
  356.     if (ct->normal < ct->purge && ndates == 1)
  357.         ct->purge = ct->normal;
  358.  
  359.     if (ct->retain < ct->normal || ct->normal < ct->purge)
  360.         die("preposterous dates: `%s'", field[2]);
  361.  
  362.     if (STREQ(field[3], "-"))
  363.         ct->dir = NULL;
  364.     else if (STREQ(field[3], "@")) {
  365.         if (defarch == NULL)
  366.             die("@ in control file but no -a", "");
  367.         ct->dir = defarch;
  368.     } else {
  369.         ct->dir = strsave(field[3]);
  370.         checkadir(ct->dir);
  371.     }
  372.  
  373.     /* put it where it belongs */
  374.     if (STREQ(ct->groups, "/expired/"))
  375.         holdover = ct;
  376.     else if (STREQ(ct->groups, "/bounds/"))
  377.         bounds = ct;
  378.     else if (ct->groups[0] == '/')
  379.         die("unknown special line name `%s'", ct->groups);
  380.     else {
  381.         ct->next = NULL;
  382.         if (lastctl == NULL)
  383.             ctls = ct;
  384.         else
  385.             lastctl->next = ct;
  386.         lastctl = ct;
  387.     }
  388. }
  389.  
  390. /*
  391.  - prime - prime control lists from active file
  392.  */
  393. void
  394. prime(afile)
  395. char *afile;
  396. {
  397.     register char *line;
  398.     register FILE *af;
  399.     register struct ctl *ct;
  400. #    define    NFACT    4
  401.     char *field[NFACT];
  402.     int nf;
  403.     register int hash;
  404.     register int i;
  405.  
  406.     af = eufopen(afile, "r");
  407.     while ((line = fgetms(af)) != NULL) {
  408.         i = strlen(line);
  409.         if (i > 0)        /* Geoff's bloody useless \n */
  410.             line[i-1] = '\0';
  411.         nf = split(line, field, NFACT, "");
  412.         if (nf != NFACT)
  413.             die("wrong number of fields in active for `%s'", field[0]);
  414.         ct = (struct ctl *)malloc(sizeof(struct ctl));
  415.         if (ct == NULL)
  416.             fail("out of memory at newsgroup `%s'", field[0]);
  417.         ct->groups = strsave(field[0]);
  418.         ct->ismod = (strchr(field[3], 'm') != NULL) ? MOD : UNMOD;
  419.         fillin(ct);
  420.         hash = strlen(field[0]);
  421.         if (hash > NHASH-1)
  422.             hash = NHASH-1;
  423.         ct->next = ngs[hash];
  424.         ngs[hash] = ct;
  425.         free(line);
  426.     }
  427.     (void) fclose(af);
  428. }
  429.  
  430. /*
  431.  - fillin - fill in a ctl struct for a newsgroup from the control-file list
  432.  */
  433. void
  434. fillin(ct)
  435. register struct ctl *ct;
  436. {
  437.     register struct ctl *cscan;
  438.     char grump[100];
  439.  
  440.     for (cscan = ctls; cscan != NULL; cscan = cscan->next)
  441.         if ((cscan->ismod == ct->ismod || cscan->ismod == EITHER) &&
  442.             ngpatmat(cscan->pat, ct->groups)) {
  443.             ct->retain = cscan->retain;
  444.             ct->normal = cscan->normal;
  445.             ct->purge = cscan->purge;
  446.             ct->dir = cscan->dir;
  447.             return;
  448.         }
  449.  
  450.     /* oooooops... */
  451.     sprintf(grump, "group `%%s' (%smoderated) not covered by control file",
  452.                     (ct->ismod == MOD) ? "" : "un");
  453.     die(grump, ct->groups);
  454. }
  455.  
  456. /*
  457.  - doit - file manipulation and master control
  458.  */
  459. void
  460. doit()
  461. {
  462.     register int old;
  463.     register FILE *new;
  464.     char *line;
  465.     long here;
  466.     register char *nameend;
  467.     datum lhs;
  468.     datum rhs;
  469.     register int ret;
  470.  
  471.     cd(histdir);
  472.     old = open("history", 0);
  473.     if (old < 0)
  474.         fail("cannot open `%s'", "history");
  475.     if (rebuild) {
  476.         (void) unlink("history.n");
  477.         (void) unlink("history.n.dir");
  478.         (void) unlink("history.n.pag");
  479.         if (spacetight)
  480.             (void) unlink("history.o");
  481.         new = eufopen("history.n", "w");
  482.         (void) fclose(eufopen("history.n.dir", "w"));
  483.         (void) fclose(eufopen("history.n.pag", "w"));
  484.         (void) dbzincore(1);
  485.         errno = 0;
  486.         if (dbzagain("history.n", "history") < 0)
  487.             fail("dbzagain(history.n) failed", "");
  488.     }
  489.  
  490.     cd(artfile((char *)NULL));
  491.     while ((line = readline(old)) != NULL) {
  492.         line = doline(line);
  493.         if (line != NULL && rebuild) {
  494.             /* extract the message-id */
  495.             nameend = strchr(line, '\t');
  496.             if (nameend != NULL) {
  497.                 /* make the DBM entry */
  498.                 *nameend = '\0';
  499.                 lhs.dptr = line;
  500.                 lhs.dsize = strlen(lhs.dptr)+1;
  501.                 here = ftell(new);
  502.                 rhs.dptr = (char *)&here;
  503.                 rhs.dsize = sizeof(here);
  504.                 ret = dbzstore(lhs, rhs);
  505.                 if (ret < 0)
  506.                     die("store failure on `%s'", line);
  507.                 *nameend = '\t';
  508.             }
  509.  
  510.             /* make the history entry */
  511.             fputs(line, new);
  512.             putc('\n', new);
  513.         }
  514.         if (line != NULL)
  515.             free(line);
  516.     }
  517.     /* side effect of readline() == NULL:  newslock() */
  518.  
  519.     (void) close(old);
  520.     if (rebuild) {
  521.         eufclose(new, "history.n");
  522.         if (dbmclose() < 0)
  523.             fail("dbmclose() failed", "");
  524.     }
  525.  
  526.     if (testing)
  527.         return;
  528.     if (rebuild) {
  529.         cd(histdir);
  530.         (void) unlink("history.o");
  531.         if (link("history", "history.o") < 0)
  532.             fail("can't move history", "");
  533.         if (unlink("history") < 0)
  534.             fail("can't finish moving history", "");
  535.         if (link("history.n", "history") < 0)
  536.             fail("disaster -- can't reinstate history!", "");
  537.         if (unlink("history.n") < 0)
  538.             fail("disaster -- can't unlink history.n!", "");
  539.         if (unlink("history.dir") < 0 && errno != ENOENT)
  540.             fail("disaster -- can't unlink history.dir!", "");
  541.         if (unlink("history.pag") < 0 && errno != ENOENT)
  542.             fail("disaster -- can't unlink history.pag!", "");
  543.         if (link("history.n.dir", "history.dir") < 0)
  544.             fail("disaster -- can't reinstate history.dir!", "");
  545.         if (link("history.n.pag", "history.pag") < 0)
  546.             fail("disaster -- can't reinstate history.pag!", "");
  547.         if (unlink("history.n.dir") < 0)
  548.             fail("disaster -- can't unlink history.n.dir!", "");
  549.         if (unlink("history.n.pag") < 0)
  550.             fail("disaster -- can't unlink history.n.pag!", "");
  551.     }
  552. }
  553.  
  554. /*
  555.  - doline - handle one history line, modifying it if appropriate
  556.  */
  557. char *                /* new (malloced) line; NULL means none */
  558. doline(line)
  559. char *line;            /* malloced; freed here */
  560. {
  561.     char *work;
  562. #    define    NF    3
  563.     char *field[NF];    /* fields in line */
  564.     register int nf;
  565. #    define    NSF    10
  566.     char *subfield[NSF];    /* subfields in middle field */
  567.     register int nsf;
  568.     register time_t recdate;
  569.     register time_t expdate;
  570.     char expbuf[25];        /* plenty for decimal time_t */
  571.     int wasreal;
  572.  
  573.     if (expdebug) {
  574.         fputs("\ndoline `", stderr);
  575.         fputs(line, stderr);
  576.         fputs("'\n", stderr);
  577.     }
  578.  
  579.     /* pull the incoming line apart */
  580.     work = strsave(line);
  581.     nf = split(work, field, NF, "\t");
  582.     if (nf != 3 && nf != 2) {
  583.         free(work);
  584.         complain("wrong number of fields in history `%.40s...'", line);
  585.         return(line);    /* leaving the line in the new history file */
  586.     }
  587.     if (*field[0] == '\0') {
  588.         complain("no message-ID in history `%.40s...' -- expiring", line);
  589.         if (strlen(field[1]) < 3) {
  590.             complain("\toops -- can't -- fix manually", "");
  591.             free(work);
  592.             return(line);    /* leaving the line in the new history file */
  593.         } else
  594.             sprintf(field[1], "0%c0", *subsep);
  595.     }
  596.     if (nf == 2)
  597.         field[2] = NULL;
  598.     nsf = split(field[1], subfield, NSF, subsep);
  599.  
  600.     /* sort out the dates */
  601.     if (nsf < 2 || STREQ(subfield[1], "-") || STREQ(subfield[1], ""))
  602.         expdate = NODATE;
  603.     else {
  604.         expdate = readdate(subfield[1]);
  605.         if (expdate == NODATE && getdgrump) {
  606.             complain("bad expiry date in `%.40s...',", line);
  607.             complain(" specifically, `%s' -- ignored", subfield[1]);
  608.         }
  609.     }
  610.     recdate = readdate(subfield[0]);
  611.     if (recdate == NODATE) {
  612.         free(work);
  613.         complain("bad arrival date in `%.40s...' -- fix by hand", line);
  614.         return(line);
  615.     }
  616.     if (recdate > latest)
  617.         latest = recdate;
  618.     free(line);
  619.     if (expdebug)
  620.         fprintf(stderr, "rec %ld, expire %ld\n", (long)recdate,
  621.                                 (long)expdate);
  622.  
  623.     /* deal with it */
  624.     if (nf > 2 && STREQ(field[2], "/"))    /* old C news cancellation */
  625.         field[2] = NULL;
  626.     else if (nf > 2 && STREQ(field[2], "cancelled"))    /* B 2.11 */
  627.         field[2] = NULL;
  628.     wasreal = (field[2] != NULL);
  629.     field[2] = doarticle(field[2], recdate, expdate, field[0]);
  630.     if (wasreal) {
  631.         if (field[2] == NULL)
  632.             ngone++;
  633.         else
  634.             nkept++;
  635.     }
  636.  
  637.     /* construct new line */
  638.     if (field[2] != NULL || (holdover != NULL &&
  639.                 !shouldgo(recdate, NODATE, holdover))) {
  640.         if (expdate != NODATE) {
  641.             sprintf(expbuf, "%ld", (long)expdate);
  642.             subfield[1] = expbuf;
  643.         } else
  644.             subfield[1] = "-";
  645.         field[1] = strvsave(subfield, nsf, *subsep);
  646.         line = strvsave(field, 3, '\t');
  647.         free(field[1]);
  648.         if (expdebug) {
  649.             fputs("new line `", stderr);
  650.             fputs(line, stderr);
  651.             fputs("'\n", stderr);
  652.         }
  653.         if (field[2] == NULL)
  654.             nresid++;
  655.     } else
  656.         line = NULL;
  657.  
  658.     free(work);
  659.     return(line);
  660. }
  661.  
  662. /*
  663.  - readdate - turn a date into internal form
  664.  */
  665. time_t
  666. readdate(text)
  667. char *text;
  668. {
  669.     time_t ret;
  670.  
  671.     if (strspn(text, "0123456789") == strlen(text))
  672.         ret = atol(text);
  673.     else
  674.         ret = getdate(text, &ftnow);
  675.     if (ret == -1)
  676.         ret = NODATE;
  677.  
  678.     return(ret);
  679. }
  680.  
  681. /*
  682.  - doarticle - possibly expire an article
  683.  *
  684.  * Re-uses the space of its first argument.
  685.  */
  686. char *                /* new name list, in space of old, or NULL */
  687. doarticle(oldnames, recdate, expdate, msgid)
  688. char *oldnames;            /* may be destroyed */
  689. time_t recdate;
  690. time_t expdate;
  691. char *msgid;            /* for printstuff() */
  692. {
  693.     register char *src;
  694.     register char *dst;
  695.     register char *name;
  696.     register char *dir;
  697.     register char *p;
  698.     register char srcc;
  699.     register int nleft;
  700.     register int nexpired;
  701. #    define    NDELIM    " ,"
  702.  
  703.     if (oldnames == NULL)
  704.         return(NULL);
  705.  
  706.     src = oldnames;
  707.     dst = oldnames;
  708.     nleft = 0;
  709.     nexpired = 0;
  710.     for (;;) {
  711.         src += strspn(src, NDELIM);
  712.         name = src;
  713.         src += strcspn(src, NDELIM);
  714.         srcc = *src;
  715.         *src = '\0';
  716.         if (*name == '\0')
  717.             break;        /* NOTE BREAK OUT */
  718.         if (expdebug)
  719.             fprintf(stderr, "name `%s'\n", name);
  720.  
  721.         dir = whereexpire(recdate, expdate, name);
  722.         if (dir != dont && !(leaders && nleft == 0 && srcc != '\0')) {
  723.             if (expdebug)
  724.                 fprintf(stderr, "expire into `%s'\n",
  725.                     (dir == NULL) ? "(null)" : dir);
  726.             for (p = strchr(name, '.'); p != NULL;
  727.                             p = strchr(p+1, '.'))
  728.                 *p = '/';
  729.             expire(name, dir);
  730.             if (dir != NULL && printexpiring)
  731.                 printstuff(msgid, name, recdate);
  732.             nexpired++;
  733.         } else {
  734.             if (dst != oldnames)
  735.                 *dst++ = ' ';
  736.             while (*name != '\0')
  737.                 *dst++ = *name++;
  738.             nleft++;
  739.         }
  740.         *src = srcc;
  741.     }
  742.  
  743.     if (nleft == 0)
  744.         return(NULL);
  745.     *dst++ = '\0';
  746.     if (leaders && nleft == 1 && nexpired > 0)    /* aging leader */
  747.         return(doarticle(oldnames, recdate, expdate, msgid));
  748.     return(oldnames);
  749. }
  750.  
  751. /*
  752.  - whereexpire - where should this name expire to, and should it?
  753.  *
  754.  * The "dont" variable's address is used as the don't-expire return value,
  755.  * since NULL means "to nowhere".
  756.  */
  757. char *                /* archive directory, NULL, or dont */
  758. whereexpire(recdate, expdate, name)
  759. time_t recdate;
  760. time_t expdate;
  761. char *name;
  762. {
  763.     register char *group;
  764.     register char *slash;
  765.     register struct ctl *ct;
  766.     register int hash;
  767.  
  768.     group = name;
  769.     slash = strchr(group, '/');
  770.     if (slash == NULL)
  771.         die("no slash in article path `%s'", name);
  772.     else
  773.         *slash = '\0';
  774.     if (strchr(slash+1, '/') != NULL) {
  775.         *slash = '/';
  776.         die("multiple slashes in article path `%s'", name);
  777.     }
  778.  
  779.     /* find applicable expiry-control struct (make it if necessary) */
  780.     hash = strlen(group);
  781.     if (hash > NHASH-1)
  782.         hash = NHASH-1;
  783.     for (ct = ngs[hash]; ct != NULL && !STREQ(ct->groups, group);
  784.                                 ct = ct->next)
  785.         continue;
  786.     if (ct == NULL) {    /* oops, there wasn't one */
  787.         if (expdebug)
  788.             fprintf(stderr, "new group `%s'\n", group);
  789.         ct = (struct ctl *)malloc(sizeof(struct ctl));
  790.         if (ct == NULL)
  791.             fail("out of memory for newsgroup `%s'", group);
  792.         ct->groups = strsave(group);
  793.         ct->ismod = UNMOD;    /* unknown -- treat it as mundane */
  794.         fillin(ct);
  795.         ct->next = ngs[hash];
  796.         ngs[hash] = ct;
  797.     }
  798.     *slash = '/';
  799.  
  800.     /* and decide */
  801.     if (!shouldgo(recdate, expdate, ct) || (holdarch && ct->dir != NULL))
  802.         return(dont);
  803.     else
  804.         return(ct->dir);
  805. }
  806.  
  807. /*
  808.  - shouldgo - should article with these dates expire now?
  809.  */
  810. int                /* predicate */
  811. shouldgo(recdate, expdate, ct)
  812. time_t recdate;
  813. time_t expdate;
  814. register struct ctl *ct;
  815. {
  816.     if (recdate >= ct->retain)    /* within retention period */
  817.         return(0);
  818.     if (recdate <= ct->purge)    /* past purge date */
  819.         return(1);
  820.     if (expdate != NODATE) {
  821.         if (now >= expdate)    /* past its explicit date */
  822.             return(1);
  823.         else
  824.             return(0);
  825.     } else {
  826.         if (recdate < ct->normal)    /* past default date */
  827.             return(1);
  828.         else
  829.             return(0);
  830.     }
  831.     /* NOTREACHED */
  832. }
  833.  
  834. /*
  835.  - expire - expire an article
  836.  */
  837. void
  838. expire(name, dir)
  839. char *name;
  840. char *dir;
  841. {
  842.     register char *old;
  843.     register char *new;
  844.     struct stat stbuf;
  845.  
  846.     if (testing) {
  847.         if (dir != NULL)
  848.             fprintf(stderr, "copy %s %s ; ", name, dir);
  849.         fprintf(stderr, "remove %s\n", name);
  850.         return;
  851.     }
  852.  
  853.     old = strsave(artfile(name));
  854.     if (dir != NULL) {
  855.         if (*dir == '=') {
  856.             new = strrchr(name, '/');
  857.             if (new == NULL)
  858.                 die("no slash in `%s'", name);
  859.             new++;
  860.             new = str3save(dir+1, "/", new);
  861.         } else
  862.             new = str3save(dir, "/", name);
  863.         /* cp() usually succeeds, so try it before getting fancy */
  864.         if (cp(old, new) < 0) {
  865.             if (stat(old, &stbuf) < 0) {
  866.                 nmissing++;
  867.                 free(old);
  868.                 free(new);
  869.                 return;        /* nonexistent */
  870.             }
  871.             if (*dir != '=')
  872.                 mkparents(name, dir);
  873.             if (cp(old, new) < 0) {
  874.                 warning("can't archive `%s'", name);
  875.                 free(old);
  876.                 free(new);
  877.                 return;        /* without removing it */
  878.             }
  879.         }
  880.         free(new);
  881.         narched++;
  882.     }
  883.     if (unlink(old) < 0) {
  884.         if (errno != ENOENT)
  885.             warning("can't remove `%s'", name);
  886.         else
  887.             nmissing++;
  888.     } else if (dir == NULL)
  889.         njunked++;
  890.     free(old);
  891. }
  892.  
  893. /*
  894.  - cp - try to copy an article
  895.  */
  896. int                /* 0 success, -1 failure */
  897. cp(src, dst)
  898. char *src;            /* absolute pathnames */
  899. char *dst;
  900. {
  901.     register int ret;
  902.     register int count;
  903.     register int in, out;
  904.     register int firstblock = 1;
  905.  
  906.     in = open(src, 0);
  907.     if (in < 0)
  908.         return(-1);
  909.     out = creat(dst, 0666);
  910.     if (out < 0) {
  911.         (void) close(in);
  912.         return(-1);
  913.     }
  914.  
  915.     while ((count = read(in, abuf, sizeof(abuf))) > 0) {
  916.         ret = write(out, abuf, count);
  917.         if (ret != count)
  918.             fail("write error in copying `%s'", src);
  919.         if (firstblock) {
  920.             getsubj(abuf, count);
  921.             firstblock = 0;
  922.         }
  923.     }
  924.     if (count < 0)
  925.         fail("read error in copying `%s'", src);
  926.  
  927.     (void) close(in);
  928.     euclose(out, dst);
  929.     return(0);
  930. }
  931.  
  932. /*
  933.  - getsubj - try to find the Subject: line in a buffer
  934.  *
  935.  * Result goes in "subject", and is never empty.  Tabs become spaces,
  936.  * since they are the output delimiters.
  937.  */
  938. void
  939. getsubj(buf, bsize)
  940. char *buf;
  941. int bsize;
  942. {
  943.     register char *scan;
  944.     register char *limit;
  945.     register int len;
  946.     register int clipped;
  947.     static char sline[] = "Subject:";
  948.  
  949.     len = strlen(sline);
  950.     limit = buf + bsize - len;
  951.     for (scan = buf; scan < limit; scan++)
  952.         if (CISTREQN(scan, sline, len) &&
  953.                 (scan == buf || *(scan-1) == '\n')) {
  954.             scan += len;
  955.             for (limit = scan; limit < buf+bsize; limit++)
  956.                 if (*limit == '\n')
  957.                     break;
  958.             while (scan < limit && isascii(*scan) && isspace(*scan))
  959.                 scan++;
  960.             len = limit-scan;
  961.             clipped = 0;
  962.             if (len > sizeof(subject)-1) {
  963.                 len = sizeof(subject) - 1 - strlen("...");
  964.                 clipped = 1;
  965.             }
  966.             if (len > 0) {
  967.                 (void) strncpy(subject, scan, len);
  968.                 subject[len] = '\0';
  969.             } else
  970.                 (void) strcpy(subject, "???");
  971.             if (clipped)
  972.                 (void) strcat(subject, "...");
  973.             for (scan = strchr(subject, '\t'); scan != NULL;
  974.                     scan = strchr(scan+1, '\t'))
  975.                 *scan = ' ';
  976.             return;
  977.         } else if (*scan == '\n' && scan+1 < limit && *(scan+1) == '\n')
  978.             break;        /* empty line terminates header */
  979.  
  980.     /* didn't find one -- fill in *something* */
  981.     (void) strcpy(subject, "???");
  982. }
  983.  
  984. /*
  985.  - mkparents - try to make directories for archiving an article
  986.  *
  987.  * Assumes it can mess with first argument if it puts it all back at the end.
  988.  */
  989. void
  990. mkparents(art, dir)
  991. char *art;            /* name relative to dir */
  992. char *dir;
  993. {
  994.     register char *cmd;
  995.     register char *ocmd;
  996.     register char *p;
  997.  
  998.     p = strchr(art, '/');
  999.     cmd = str3save(binfile((char *)NULL), "/expire/mkadir ", dir);
  1000.     while (p != NULL) {
  1001.         *p = '\0';
  1002.         ocmd = cmd;
  1003.         cmd = str3save(ocmd, " ", art);
  1004.         free(ocmd);
  1005.         *p = '/';
  1006.         p = strchr(p+1, '/');
  1007.     }
  1008.     (void) system(cmd);
  1009.     free(cmd);
  1010. }
  1011.  
  1012. char *months[12] = {
  1013.     "Jan",
  1014.     "Feb",
  1015.     "Mar",
  1016.     "Apr",
  1017.     "May",
  1018.     "Jun",
  1019.     "Jul",
  1020.     "Aug",
  1021.     "Sep",
  1022.     "Oct",
  1023.     "Nov",
  1024.     "Dec",
  1025. };
  1026.  
  1027. /*
  1028.  - printstuff - print information about an expiring article
  1029.  */
  1030. void
  1031. printstuff(msgid, name, recdate)
  1032. char *msgid;
  1033. char *name;
  1034. time_t recdate;
  1035. {
  1036.     struct tm *gmt;
  1037.  
  1038.     gmt = gmtime(&recdate);
  1039.     printf("%s\t%s\t%d-%s-%d\t%s\n", name, msgid, gmt->tm_mday,
  1040.             months[gmt->tm_mon], gmt->tm_year+1900, subject);
  1041. }
  1042.  
  1043. /*
  1044.  - eufopen - fopen, with fail if doesn't succeed
  1045.  */
  1046. FILE *
  1047. eufopen(name, mode)
  1048. char *name;
  1049. char *mode;
  1050. {
  1051.     FILE *f;
  1052.     static char grump[50] = "can't open `%s' for `";
  1053.  
  1054.     f = fopen(name, mode);
  1055.     if (f == NULL) {
  1056.         (void) strcat(grump, mode);
  1057.         (void) strcat(grump, "'");
  1058.         fail(grump, name);
  1059.     }
  1060.     return(f);
  1061. }
  1062.  
  1063. /*
  1064.  - eufclose - fclose with failure checking
  1065.  */
  1066. void
  1067. eufclose(f, name)
  1068. FILE *f;
  1069. char *name;
  1070. {
  1071.     if (nfclose(f) == EOF)
  1072.         fail("error in closing file `%s'", name);
  1073. }
  1074.  
  1075. /*
  1076.  - euclose - close with failure checking
  1077.  */
  1078. void
  1079. euclose(f, name)
  1080. int f;
  1081. char *name;
  1082. {
  1083.     if (fsync(f) < 0 || close(f) < 0)
  1084.         fail("error in closing file `%s'", name);
  1085. }
  1086.  
  1087. /*
  1088.  - checkadir - check archiving directory is real, writable, and full pathname
  1089.  */
  1090. void                /* set -h if not */
  1091. checkadir(dir)
  1092. char *dir;
  1093. {
  1094.     struct stat stbuf;
  1095.     register int hforce = 0;
  1096. #    define    GRUMP(a,b)    {warning(a, b); hforce = 1;}
  1097.  
  1098.     if (*dir == '=')    /* disregard leading '=' */
  1099.         dir++;
  1100.     errno = 0;
  1101.     if (stat(dir, &stbuf) < 0)
  1102.         GRUMP("archiving directory `%s' does not exist", dir);
  1103.     if (access(dir, 02) < 0)
  1104.         GRUMP("archiving directory `%s' not writable", dir);
  1105.     if (dir[0] != '/')
  1106.         GRUMP("archiving directory `%s' not a full pathname", dir);
  1107.     if (hforce) {
  1108.         warning("forcing -h option as a stopgap", "");
  1109.         holdarch = 1;
  1110.     }
  1111. }
  1112.  
  1113. /*
  1114.  - back - get a date n days back, with overflow check
  1115.  *
  1116.  * Requires that "now" be set first.
  1117.  */
  1118. time_t
  1119. back(ndaystr)
  1120. char *ndaystr;
  1121. {
  1122.     register double goback;        /* how far before now it is */
  1123.  
  1124.     if (STREQ(ndaystr, "never"))
  1125.         goback = FOREVER;    /* > now-EPOCH */
  1126.     else
  1127.         goback = atof(ndaystr) * DAY;
  1128.  
  1129.     if (goback > now-EPOCH)        /* before EPOCH */
  1130.         return(EPOCH);
  1131.     return((time_t)(now - goback));
  1132. }
  1133.  
  1134. /*
  1135.  - printlists - print control lists for debugging
  1136.  */
  1137. void
  1138. printlists()
  1139. {
  1140.     register int i;
  1141.     register struct ctl *ct;
  1142.  
  1143.     fprintf(stderr, "control file:\n");
  1144.     for (ct = ctls; ct != NULL; ct = ct->next)
  1145.         pctl(ct);
  1146.     fprintf(stderr, "\n");
  1147.  
  1148.     for (i = 0; i < NHASH; i++)
  1149.         if (ngs[i] != NULL) {
  1150.             fprintf(stderr, "list %d:\n", i);
  1151.             for (ct = ngs[i]; ct != NULL; ct = ct->next)
  1152.                 pctl(ct);
  1153.         }
  1154.     fprintf(stderr, "\n");
  1155. }
  1156.  
  1157. /*
  1158.  - pctl - print one control-list entry
  1159.  */
  1160. void
  1161. pctl(ct)
  1162. register struct ctl *ct;
  1163. {
  1164. #    define    DAYS(x)    ((now-(x))/DAY)
  1165.  
  1166.     fprintf(stderr, "%s(%c) %.2f-%.2f-%.2f %s\n", ct->groups, ct->ismod,
  1167.             DAYS(ct->retain), DAYS(ct->normal), DAYS(ct->purge),
  1168.             (ct->dir == NULL) ? "(null)" : ct->dir);
  1169. }
  1170.  
  1171. /*
  1172.  - unprivileged - no-op needed to keep the pathname stuff happy
  1173.  */
  1174. void
  1175. unprivileged(reason)
  1176. char *reason;
  1177. {
  1178. }
  1179.  
  1180. /*
  1181.  - fail - call errunlock, possibly after cleanup
  1182.  */
  1183. void
  1184. fail(s1, s2)
  1185. char *s1;
  1186. char *s2;
  1187. {
  1188.     int saveerr = errno;
  1189.  
  1190.     if (spacetight) {
  1191.         cd(histdir);
  1192.         (void) unlink("history.n");
  1193.         (void) unlink("history.n.dir");
  1194.         (void) unlink("history.n.pag");
  1195.     }
  1196.     errno = saveerr;
  1197.     errunlock(s1, s2);
  1198.     /* NOTREACHED */
  1199. }
  1200.  
  1201. /*
  1202.  - die - like fail, but errno contains no information
  1203.  */
  1204. void
  1205. die(s1, s2)
  1206. char *s1;
  1207. char *s2;
  1208. {
  1209.     errno = 0;
  1210.     fail(s1, s2);
  1211. }
  1212.  
  1213. /*
  1214.  - readline - read history line (sans newline), with locking when we hit EOF
  1215.  *
  1216.  * Minor flaw:  will lose a last line which lacks a newline.
  1217.  */
  1218. char *                /* NULL is EOF */
  1219. readline(fd)
  1220. int fd;                /* Note descriptor, not FILE *. */
  1221. {
  1222.     register char *line;
  1223.     register int nline;
  1224.     register char *p;
  1225.     register int c;
  1226.     register int n;
  1227.     extern void refill();
  1228.  
  1229.     nline = 100;        /* reasonable starter */
  1230.     line = malloc(nline);
  1231.     if (line == NULL)
  1232.         fail("out of space when reading history", "");
  1233.     p = line;
  1234.  
  1235.     for (;;) {
  1236.         if (rlnleft <= 0) {
  1237.             refill(fd);
  1238.             if (rlnleft <= 0)    /* refill gave up. */
  1239.                 return(NULL);
  1240.         }
  1241.         c = *rest++;
  1242.         rlnleft--;
  1243.  
  1244.         if (c == '\n') {
  1245.             *p++ = '\0';
  1246.             return(line);
  1247.         }
  1248.         if (p - line >= nline - 1) {
  1249.             nline = (nline * 3) / 2;
  1250.             n = p - line;
  1251.             line = realloc(line, nline);
  1252.             if (line == NULL)
  1253.                 fail("out of memory in readline", "");
  1254.             p = line + n;
  1255.         }
  1256.         *p++ = c;
  1257.     }
  1258.     /* NOTREACHED */
  1259. }
  1260.  
  1261. /*
  1262.  - refill - refill readline's buffer, with locking on EOF
  1263.  */
  1264. void
  1265. refill(fd)
  1266. int fd;
  1267. {
  1268.     register int ret;
  1269.  
  1270.     /* Just in case... */
  1271.     if (rlnleft > 0)
  1272.         return;
  1273.  
  1274.     /* Try ordinary read. */
  1275.     ret = read(fd, rlbuf, (int)sizeof(rlbuf));
  1276.     if (ret < 0)
  1277.         fail("read error in history", "");
  1278.     if (ret > 0) {
  1279.         rlnleft = ret;
  1280.         rest = rlbuf;
  1281.         return;
  1282.     }
  1283.  
  1284.     /* EOF. */
  1285.     if (nlocked)
  1286.         return;        /* We're really done. */
  1287.  
  1288.     /* EOF but we haven't locked yet.  Lock and try again. */
  1289.     (void) signal(SIGINT, SIG_IGN);
  1290.     (void) signal(SIGQUIT, SIG_IGN);
  1291.     (void) signal(SIGHUP, SIG_IGN);
  1292.     (void) signal(SIGTERM, SIG_IGN);
  1293.     newslock();
  1294.     nlocked = 1;
  1295.     refill(fd);
  1296. }
  1297.  
  1298. /*
  1299.  - strvsave - sort of like strsave, but for a vector of strings
  1300.  */
  1301. char *
  1302. strvsave(v, nv, delim)
  1303. char **v;
  1304. int nv;
  1305. char delim;
  1306. {
  1307.     register char **p;
  1308.     register int i;
  1309.     register char *result;
  1310.     register char *rp;
  1311.     register int len;
  1312.  
  1313.     if (nv <= 0)
  1314.         return(strsave(""));
  1315.  
  1316.     len = 0;
  1317.     for (i = nv, p = v; i > 0; i--, p++)
  1318.         if (*p != NULL)
  1319.             len += strlen(*p) + 1;
  1320.     result = malloc(len);
  1321.     if (result == NULL)
  1322.         fail("out of memory in strvsave", "");
  1323.  
  1324.     rp = result;
  1325.     for (i = nv, p = v; i > 0; i--, p++)
  1326.         if (*p != NULL) {
  1327.             (void) strcpy(rp, *p);
  1328.             rp += strlen(rp);
  1329.             *rp++ = delim;
  1330.         }
  1331.     rp--;
  1332.     *rp = '\0';
  1333.  
  1334.     return(result);
  1335. }
  1336.